/**
 * 
 */
package be.gwi.alfresco.run.service.impl;

import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import be.gwi.alfresco.run.exception.FacadeException;
import be.gwi.alfresco.run.exception.ServiceException;
import be.gwi.alfresco.run.facade.RunFacade;
import be.gwi.alfresco.run.facade.RunnerFacade;
import be.gwi.alfresco.run.helper.RunHelper;
import be.gwi.alfresco.run.model.Run;
import be.gwi.alfresco.run.model.RunType;
import be.gwi.alfresco.run.model.Runner;
import be.gwi.alfresco.run.model.comparator.RunComparator;
import be.gwi.alfresco.run.model.comparator.RunnerComparator;
import be.gwi.alfresco.run.service.RunnerService;

/**
 * 
 * @author gabriel.wilems@gmail.com
 * @see RunnerService
 * 
 * @version 1.0
 * @since 1.0
 */
public class RunnerServiceImpl implements RunnerService {
    private static Log   logger = LogFactory.getLog(RunnerServiceImpl.class);

    private RunHelper    runHelper;
    private RunnerFacade runnerFacade;
    private RunFacade    runFacade;

    /**
     * Initializes the service.
     * 
     * Verifies that all the attributes have been set.
     * 
     * @throws IllegalArgumentException
     *             if not all attributes have been set.
     */
    public void init() {
        Assert.notNull(this.runHelper, "The run helper must have been set");
        Assert.notNull(this.runnerFacade, "The runner facade must have been set");
        Assert.notNull(this.runFacade, "The run facade must have been set");
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#getRunner(java.lang.String)
     */
    @Override
    public Runner getRunner(String username) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getRunner(" + username + ")");
        }
        try {
            return this.runnerFacade.getRunner(username);
        } catch (FacadeException e) {
            throw new ServiceException("Fail to get current runner", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see be.gwi.alfresco.run.service.RunnerService#getCurrentRunner()
     */
    @Override
    public Runner getCurrentRunner() throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getCurrentRunner()");
        }
        String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
        try {
            return this.runnerFacade.getRunner(currentUser);
        } catch (FacadeException e) {
            throw new ServiceException("Fail to get current runner", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#addRun(be.gwi.alfresco.run.
     * model.Run)
     */
    @Override
    public void addRun(final Run run) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.addRun(" + run + ")");
        }
        final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
        try {
            // Do the work as admin
            AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Void>() {
                /**
                 * Executes the invokable target.
                 */
                public Void doWork() throws Exception {
                    addRunWork(run, currentUser);
                    return null;
                }
            }, AuthenticationUtil.getSystemUserName());
        } catch (Exception e) {
            throw new ServiceException("Fail to addRun", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#getHistory(java.lang.String,
     * java.lang.String)
     */
    @Override
    public List<Run> getHistory(String username, String year) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getHistory(" + username + ", " + year + ")");
        }
        try {
            if (!StringUtils.hasText(year)) {
                year = String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
                if (logger.isDebugEnabled()) {
                    logger.debug("No year provided, current year: " + year);
                }
            }
            Runner runner = runnerFacade.getRunner(username);
            List<Run> history = runFacade.getHistory(runner, year);
            Collections.sort(history, new RunComparator());
            Collections.reverse(history);
            return history;
        } catch (FacadeException e) {
            throw new ServiceException("Fail to get history", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#getCurrentHistory(java.lang
     * .String)
     */
    @Override
    public List<Run> getCurrentHistory(String year) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getCurrentHistory(" + year + ")");
        }
        String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
        return this.getHistory(currentUser, year);
    }
    
    /*
     * (non-Javadoc)
     * @see be.gwi.alfresco.run.service.RunnerService#getHistoryByRunType(java.lang.String, java.lang.String)
     */
    @Override
    public List<Run> getHistoryByRunType(String username, RunType runType) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getHistoryByRunType(" + username + ", " + runType + ")");
        }
        try {
            Runner runner = runnerFacade.getRunner(username);
            List<Run> history = runFacade.getHistoryByRunType(runner, runType);
            Collections.sort(history, new RunComparator());
            Collections.reverse(history);
            return history;
        } catch (FacadeException e) {
            throw new ServiceException("Fail to get history", e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#getCurrentHistoryByRunType(
     * java.lang.String)
     */
    @Override
    public List<Run> getCurrentHistoryByRunType(RunType runType) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getCurrentHistoryByRunType(" + runType + ")");
        }
        String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
        return this.getHistoryByRunType(currentUser, runType);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.service.RunnerService#getRanking(be.gwi.alfresco.
     * run.model.RunType)
     */
    @Override
    public List<Runner> getRanking(RunType runType) throws ServiceException {
        if (logger.isDebugEnabled()) {
            logger.debug("Call RunnerServiceImpl.getRanking(" + runType + ")");
        }
        List<Runner> runnerList;
        try {
            runnerList = this.runnerFacade.getRunners();
            Collections.sort(runnerList, new RunnerComparator(runType));
            Collections.reverse(runnerList);
        } catch (FacadeException e) {
            throw new ServiceException("Fail to get ranking", e);
        }
        return runnerList;
    }

    /**
     * Add Run real work
     * 
     * This method should be called as admin
     * 
     * @param run
     * @param currentUser
     * @throws FacadeException
     */
    private void addRunWork(Run run, String currentUser) throws FacadeException {
        if (!this.runnerFacade.isRunner(currentUser)) {
            this.runnerFacade.initRunner(currentUser);
        }
        Runner runner = this.runnerFacade.getRunner(currentUser);
        // Add run to history
        this.runFacade.addRunToRunnerHistory(run, runner);
        // Update runner stats
        this.runHelper.addRunToRunner(run, runner);
        // Save runner
        this.runnerFacade.updateRunner(runner);
    }

    /**
     * @param runnerFacade
     *            the runnerFacade to set
     */
    public void setRunnerFacade(RunnerFacade runnerFacade) {
        this.runnerFacade = runnerFacade;
    }

    /**
     * @param runFacade
     *            the runFacade to set
     */
    public void setRunFacade(RunFacade runFacade) {
        this.runFacade = runFacade;
    }

    /**
     * @param runnerHelper
     *            the runnerHelper to set
     */
    public void setRunHelper(RunHelper runHelper) {
        this.runHelper = runHelper;
    }
}
